/*
 * Tencent is pleased to support the open source community by making Spring Cloud Tencent available.
 *
 * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the BSD 3-Clause License (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://opensource.org/licenses/BSD-3-Clause
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 *
 */

package com.tencent.cloud.polaris.ratelimit.resolver;

import java.net.InetSocketAddress;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;


import com.tencent.cloud.common.metadata.MetadataContextHolder;
import com.tencent.cloud.polaris.context.ServiceRuleManager;
import com.tencent.cloud.polaris.ratelimit.spi.PolarisRateLimiterLabelReactiveResolver;
import com.tencent.polaris.ratelimit.api.rpc.Argument;
import com.tencent.polaris.specification.api.v1.traffic.manage.RateLimitProto;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;

import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAME;
import static com.tencent.cloud.common.constant.MetadataConstant.DefaultMetadata.DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE;

/**
 * resolve arguments from rate limit rule for Reactive.
 *
 * @author seansyyu 2023-03-09
 */
public class RateLimitRuleArgumentReactiveResolver {

	private static final Logger LOG = LoggerFactory.getLogger(RateLimitRuleArgumentReactiveResolver.class);

	private final ServiceRuleManager serviceRuleManager;

	private final PolarisRateLimiterLabelReactiveResolver labelResolver;

	public RateLimitRuleArgumentReactiveResolver(ServiceRuleManager serviceRuleManager, PolarisRateLimiterLabelReactiveResolver labelResolver) {
		this.serviceRuleManager = serviceRuleManager;
		this.labelResolver = labelResolver;
	}

	public Set<Argument> getArguments(ServerWebExchange request, String namespace, String service) {
		RateLimitProto.RateLimit rateLimitRule = serviceRuleManager.getServiceRateLimitRule(namespace, service);
		if (rateLimitRule == null) {
			return Collections.emptySet();
		}
		List<RateLimitProto.Rule> rules = rateLimitRule.getRulesList();
		if (CollectionUtils.isEmpty(rules)) {
			return Collections.emptySet();
		}
		return rules.stream()
				.flatMap(rule -> rule.getArgumentsList().stream())
				.map(matchArgument -> {
					String matchKey = matchArgument.getKey();
					Argument argument = null;
					switch (matchArgument.getType()) {
					case CUSTOM:
						argument = StringUtils.isBlank(matchKey) ? null :
								Argument.buildCustom(matchKey, Optional.ofNullable(getCustomResolvedLabels(request).get(matchKey)).orElse(StringUtils.EMPTY));
						break;
					case METHOD:
						argument = Argument.buildMethod(request.getRequest().getMethodValue());
						break;
					case HEADER:
						argument = StringUtils.isBlank(matchKey) ? null :
								Argument.buildHeader(matchKey, Optional.ofNullable(request.getRequest().getHeaders().getFirst(matchKey)).orElse(StringUtils.EMPTY));
						break;
					case QUERY:
						argument = StringUtils.isBlank(matchKey) ? null :
								Argument.buildQuery(matchKey, Optional.ofNullable(request.getRequest().getQueryParams().getFirst(matchKey)).orElse(StringUtils.EMPTY));
						break;
					case CALLER_SERVICE:
						String sourceServiceNamespace = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAMESPACE, true).orElse(StringUtils.EMPTY);
						String sourceServiceName = MetadataContextHolder.getDisposableMetadata(DEFAULT_METADATA_SOURCE_SERVICE_NAME, true).orElse(StringUtils.EMPTY);
						if (!StringUtils.isEmpty(sourceServiceNamespace) && !StringUtils.isEmpty(sourceServiceName)) {
							argument = Argument.buildCallerService(sourceServiceNamespace, sourceServiceName);
						}
						break;
					case CALLER_IP:
						InetSocketAddress remoteAddress = request.getRequest().getRemoteAddress();
						argument = Argument.buildCallerIP(remoteAddress != null ? remoteAddress.getAddress().getHostAddress() : StringUtils.EMPTY);
						break;
					default:
						break;
					}
					return argument;
				}).filter(Objects::nonNull).collect(Collectors.toSet());
	}

	private Map<String, String> getCustomResolvedLabels(ServerWebExchange request) {
		if (labelResolver != null) {
			try {
				return labelResolver.resolve(request);
			}
			catch (Throwable e) {
				LOG.error("resolve custom label failed. resolver = {}", labelResolver.getClass().getName(), e);
			}
		}
		return Collections.emptyMap();
	}
}
